#include "Application.h"

#include <OgreWindowEventUtilities.h>
#include <OgreEntity.h>
#include <OgreRenderSystem.h>

Application::Application()
{
	// Make all the used pointers NULL
	ogre                        = NULL;
	window                      = NULL;
	sceneManager                = NULL;
	camera                      = NULL;
	viewport                    = NULL;
	ois                         = NULL;
	mouse                       = NULL;
	keyboard                    = NULL;
	cursor                      = NULL;
	debug                       = NULL;
	picker                      = NULL;
	factory                     = NULL;
	firstPersonCameraController = NULL;
	world                       = NULL;
	defaultMaterialPair         = NULL;

	// Set desired framerate and variables used for stepping the physics
	desiredPhysicsFramerate     = 60;
	lastFPS                     = 0;
	lastFrameDuration           = 0;
	physicsUpdateStep           = 1.0f / static_cast<float>(desiredPhysicsFramerate);
	physicsTimeAccumulator      = 0.0f;

	// Application is not yet running and debugger is not activated by default
	running                     = false;
	debuggerActive              = false;
}

Application::~Application()
{
	// Clean everything up in reverse order these were created
	if (defaultMaterialPair)
	{
		delete defaultMaterialPair;
		defaultMaterialPair = NULL;
	}

	if (factory)
	{
		delete factory;
		factory = NULL;
	}

	if (picker)
	{
		delete picker;
		picker = NULL;
	}

	if (world)
	{
		world->getDebugger().deInit();

		delete world;
		world = NULL;
	}

	if (firstPersonCameraController)
	{
		delete firstPersonCameraController;
		firstPersonCameraController = NULL;
	}

	if (debug)
	{
		delete debug;
		debug = NULL;
	}

	if (cursor)
	{
		delete cursor;
		cursor = NULL;
	}

	if (mouse)
	{
		ois->destroyInputObject(mouse);
		mouse = NULL;
	}

	if (keyboard)
	{
		ois->destroyInputObject(keyboard);
		keyboard = NULL;
	}
    
	if (ois)
	{
		ois->destroyInputObject(mouse);
		mouse = NULL;

		ois->destroyInputObject(keyboard);
		keyboard = NULL;

		OIS::InputManager::destroyInputSystem(ois);
		ois = NULL;
	}

	if (ogre)
	{
		delete ogre;
		ogre = NULL;
	}
}

bool Application::initialise()
{
	// Initialise Ogre and load plugins based on debug mode
	ogre = new Ogre::Root("", "ogre.cfg", "ogre.log");

#ifdef _DEBUG
	ogre->loadPlugin("RenderSystem_Direct3D9_d");
	ogre->loadPlugin("RenderSystem_GL_d");
	ogre->loadPlugin("Plugin_CgProgramManager_d");
#else
	ogre->loadPlugin("RenderSystem_Direct3D9");
	ogre->loadPlugin("RenderSystem_GL");
	ogre->loadPlugin("Plugin_CgProgramManager");
#endif;

	// Show the configuration dialog, exit if user cancels
	if (!ogre->showConfigDialog())
	{
		return false;
	}

	// Initialise Ogre and start listening window events
	window = ogre->initialise(true, "OgreNewt Minimal Application");
	Ogre::WindowEventUtilities::addWindowEventListener(window, this);

	// Add used resource groups
	Ogre::ResourceGroupManager::getSingleton().addResourceLocation("media/gui", "FileSystem", "General");
	Ogre::ResourceGroupManager::getSingleton().addResourceLocation("media/materials/scripts", "FileSystem", "General");
	Ogre::ResourceGroupManager::getSingleton().addResourceLocation("media/fonts", "FileSystem", "General");
	Ogre::ResourceGroupManager::getSingleton().addResourceLocation("media/primitives", "FileSystem", "General");
	Ogre::ResourceGroupManager::getSingleton().addResourceLocation("media/meshes", "FileSystem", "General");

	// Initialise the resource groups
	Ogre::ResourceGroupManager::getSingleton().initialiseResourceGroup("General");

	// Create a generic scene manager
	sceneManager = ogre->createSceneManager(Ogre::ST_GENERIC);

	// Create a camera
	camera = sceneManager->createCamera("camera");
	camera->setNearClipDistance(0.1f);
	camera->setFarClipDistance(250.0f);

	// Create a first person camera controller
	firstPersonCameraController = new FirstPersonCameraController(sceneManager);

	// Add main viewport and set its background colour
    viewport = window->addViewport(camera);
    viewport->setBackgroundColour(Ogre::ColourValue(0.5f, 0.5f, 0.5f));

	// Set camera aspect ratio based on viewport size
	camera->setAspectRatio(Ogre::Real(viewport->getActualWidth()) / Ogre::Real(viewport->getActualHeight()));

	// Get window handle, needed for OIS
	unsigned long windowHandle;
	window->getCustomAttribute("WINDOW", &windowHandle);

	// Feed OIS window handle needed to capture events
	OIS::ParamList oisParameters;
	oisParameters.insert(OIS::ParamList::value_type("WINDOW", Ogre::StringConverter::toString(windowHandle)));

	// Create an OIS input system
	ois = OIS::InputManager::createInputSystem(oisParameters);

	// Create mouse and keyboard, joystick can be made in the same manner
	mouse = static_cast<OIS::Mouse*>(ois->createInputObject(OIS::OISMouse, true));
	keyboard = static_cast<OIS::Keyboard*>(ois->createInputObject(OIS::OISKeyboard, true));

	// Start listening for mouse and keyboard events
	mouse->setEventCallback(this);
	keyboard->setEventCallback(this);

	// Create a custom mouse cursor that uses Ogre overlays
	cursor = new MouseCursor();
	cursor->setImage("cursor.png");
    cursor->setWindowDimensions(window->getWidth(), window->getHeight());
	cursor->setVisible(true);

	// Create and initialize a debug screenwriter, that enables writing simple text on screen
	debug = new ScreenWriter(viewport->getActualWidth(), viewport->getActualHeight());
	debug->init();

	// Tell mouse the size of out window
	const OIS::MouseState& mouseState = mouse->getMouseState();
	mouseState.width = window->getWidth();
	mouseState.height = window->getHeight();

	// Create a new OgreNewt physics world and set some basic configuration options, initialise debugger
	world = new OgreNewt::World();
	world->setWorldSize(Ogre::Vector3(-250, -250, -250), Ogre::Vector3(250, 250, 250));
	world->setSolverModel(OgreNewt::World::SM_FASTEST);
	world->setThreadCount(1);
	world->getDebugger().init(sceneManager);

	// Create a new picker, hold down CTRL to use it
	picker = new Picker();
	picker->init(sceneManager, world, keyboard, mouse, 250, 10);

	// Create a primitive factory, use singleton to access it later
	factory = new PrimitiveFactory(world, sceneManager);

	// Create default material pair
	const OgreNewt::MaterialID* defaultMaterialId = world->getDefaultMaterialID();
    defaultMaterialPair = new OgreNewt::MaterialPair(world, defaultMaterialId, defaultMaterialId);

	// Set default material settings
	defaultMaterialPair->setDefaultElasticity(0.4f);
    defaultMaterialPair->setDefaultSoftness(0.05f);
    defaultMaterialPair->setDefaultFriction(0.9f, 0.6f);
    defaultMaterialPair->setDefaultCollidable(1);
    defaultMaterialPair->setContinuousCollisionMode(0);

	// Write some help info on the screen
	debug->write(-280, 20, "F1         - Toggle debug display");
	debug->write(-280, 36, "Hold CTRL  - Show cursor and pick");
	debug->write(-280, 52, "Hold SHIFT - Move faster");
	debug->write(-280, 68, "SPACE      - Throw a sphere");
	debug->write(-280, 84, "ESC        - Exit application");

	return true;
}

bool Application::setup()
{
	// Set the ammount and colour of ambient light in the scene
	sceneManager->setAmbientLight(Ogre::ColourValue(0.35f, 0.35f, 0.35f));

	// Set camera position
	firstPersonCameraController->setPosition(Ogre::Vector3(0, 2, 20));

	// Create a point-light
	Ogre::Light* light = sceneManager->createLight("Light");
	light->setType(Ogre::Light::LT_POINT);
	light->setPosition(Ogre::Vector3(100, 150, 100));

	// Create a body out of an Ogre node
	Ogre::Entity* entity = sceneManager->createEntity("Cylinder", "cylinder.mesh");
	entity->setMaterialName("Debug/Red");
	Ogre::SceneNode* node = sceneManager->getRootSceneNode()->createChildSceneNode("Cylinder");
	node->attachObject(entity);
	node->setScale(Ogre::Vector3(7, 1, 3));
	node->setPosition(Ogre::Vector3(0, 0.5f, 0));
	OgreNewt::Body* spinner = PrimitiveFactory::getSingleton().createCylinder(node, 100);
	spinner->setAutoSleep(0);

	spinnerHinge = new OgreNewt::Hinge(spinner, NULL, node->getPosition(), Ogre::Vector3::UNIT_Y);

	// Create static tree-collision using the factory
	OgreNewt::Body* sandbox = PrimitiveFactory::getSingleton().createTreeCollision("sandbox.mesh", "sandbox", "Debug/Green", Ogre::Vector3::ZERO, Ogre::Vector3::UNIT_SCALE, Ogre::Quaternion::IDENTITY, NULL, 0, false);

	for (int x = 0; x < 5; x++)
	{
		for (int y = 0; y < 5; y++)
		{
			// Create some boxes using the factory
			PrimitiveFactory::getSingleton().createBox("", (((x % 2 == 0) ^ (y % 2 == 0)) ? "Debug/Blue" : "Debug/Red"), 1.0f, Ogre::Vector3(x - 2.25f, y + 1.5f, 0.0f));
		}
	}


	// Create a body manually
	entity = sceneManager->createEntity("Ellipsoid", "ellipsoid.mesh");
	entity->setMaterialName("Debug/Blue");
	entity->setCastShadows(true);
	node = sceneManager->getRootSceneNode()->createChildSceneNode("Ellipsoid");
	node->attachObject(entity);
	node->setScale(Ogre::Vector3(5, 5, 5));

	OgreNewt::ConvexCollisionPtr collision = OgreNewt::ConvexCollisionPtr(new OgreNewt::CollisionPrimitives::Ellipsoid(world, node->getScale() / 2, 1));
	OgreNewt::Body* body = new OgreNewt::Body(world, collision);
	body->attachNode(node);
	body->setPositionOrientation(Ogre::Vector3(0.0f, 5.0f, -10.0f), Ogre::Quaternion::IDENTITY);
	
	Ogre::Vector3 inertia, centerOfMass;
	collision->calculateInertialMatrix(inertia, centerOfMass);
	body->setMassMatrix(1.0f, inertia * 1.0f);
	body->setCenterOfMass(centerOfMass);
	body->setStandardForceCallback();

	// Lets create a simple vehicle-like object
	Ogre::Vector3 pos = Ogre::Vector3(0, 1, 10);

	// Create two frame parts
	OgreNewt::Body* rearFrame = PrimitiveFactory::getSingleton().createBox("RearFrame", "Debug/Blue", 600, pos + Ogre::Vector3(0.0f, 0.0f, 1.5f), Ogre::Vector3(2.0f, 1.0f, 2.75f));
	OgreNewt::Body* frontFrame = PrimitiveFactory::getSingleton().createBox("FrontFrame", "Debug/Blue", 600, pos + Ogre::Vector3(0.0f, 0.0f, -1.5f), Ogre::Vector3(2.0f, 1.0f, 2.75f));

	// Create the wheels
	OgreNewt::Body* wheelRR = PrimitiveFactory::getSingleton().createCylinder("WheelRR", "Debug/Red", 20, pos + Ogre::Vector3(1.35f, 0.0f, 2.0f), 1.0f, 0.6f, Ogre::Quaternion(Ogre::Degree(90), Ogre::Vector3::UNIT_Z));
	OgreNewt::Body* wheelRL = PrimitiveFactory::getSingleton().createCylinder("WheelRL", "Debug/Red", 20, pos + Ogre::Vector3(-1.35f, 0.0f, 2.0f), 1.0f, 0.6f, Ogre::Quaternion(Ogre::Degree(90), Ogre::Vector3::UNIT_Z));
	OgreNewt::Body* wheelFR = PrimitiveFactory::getSingleton().createCylinder("WheelFR", "Debug/Red", 20, pos + Ogre::Vector3(1.35f, 0.0f, -2.0f), 1.0f, 0.6f, Ogre::Quaternion(Ogre::Degree(90), Ogre::Vector3::UNIT_Z));
	OgreNewt::Body* wheelFL = PrimitiveFactory::getSingleton().createCylinder("WheelFL", "Debug/Red", 20, pos + Ogre::Vector3(-1.35f, 0.0f, -2.0f), 1.0f, 0.6f, Ogre::Quaternion(Ogre::Degree(90), Ogre::Vector3::UNIT_Z));

	// Create hinges to attach the wheels to the frame
	OgreNewt::Joint* wheelRRhinge = new OgreNewt::Hinge(wheelRR, rearFrame, pos + Ogre::Vector3(1.35f, 0.0f, 2.0f), Ogre::Vector3::UNIT_X);
	OgreNewt::Joint* wheelRLhinge = new OgreNewt::Hinge(wheelRL, rearFrame, pos + Ogre::Vector3(-1.35f, 0.0f, 2.0f), Ogre::Vector3::UNIT_X);
	OgreNewt::Joint* wheelFRhinge = new OgreNewt::Hinge(wheelFR, frontFrame, pos + Ogre::Vector3(1.35f, 0.0f, -2.0f), Ogre::Vector3::UNIT_X);
	OgreNewt::Joint* wheelFLhinge = new OgreNewt::Hinge(wheelFL, frontFrame, pos + Ogre::Vector3(-1.35f, 0.0f, -2.0f), Ogre::Vector3::UNIT_X);

	// Create a center hinge to attach the two frames and apply some limits
	OgreNewt::Hinge* centerHinge = new OgreNewt::Hinge(frontFrame, rearFrame, pos, Ogre::Vector3::UNIT_Z);
	centerHinge->enableLimits(true);
	centerHinge->setLimits(Ogre::Degree(-60), Ogre::Degree(60));

	return true;
}

void Application::run()
{
	// Mark the application running
	running = true;

	while (running)
	{
		// Pump window messages for nice behaviour
		Ogre::WindowEventUtilities::messagePump();

		// Capture input
		mouse->capture();
		keyboard->capture();

		// Get frame rendertime in milliseconds
		unsigned long currentFrameTime = ogre->getTimer()->getMilliseconds();
		lastFrameDuration = currentFrameTime - lastFrameTime;
		lastFrameTime = currentFrameTime;

		// Add last frame duration to accumulator that uses seconds
		physicsTimeAccumulator += lastFrameDuration / 1000.0f;

		// Check whether there is any reason to step the physics
		if (physicsTimeAccumulator > physicsUpdateStep)
		{
			// Lets make maximum of 5 physics updates
			if (physicsTimeAccumulator < physicsUpdateStep * 5)
			{
				while (physicsTimeAccumulator >= physicsUpdateStep)
				{
					// Update the physics world and substract from the accumulator
					world->update(physicsUpdateStep);
					physicsTimeAccumulator -= physicsUpdateStep;
				}
			}
			else
			{
				// Too many steps, just update once and clear accumulator to prevent total slowdown
				world->update(physicsTimeAccumulator);
				physicsTimeAccumulator = 0.0f;
			}
		}

		// Apply desired omega of 5deg/s for the spinning platform
		spinnerHinge->setDesiredOmega(Ogre::Degree(5));

		// Check whether CTRL is being held down
		if (keyboard->isKeyDown(OIS::KC_LCONTROL) || keyboard->isKeyDown(OIS::KC_RCONTROL))
		{
			// If so, make the cursor visible and enable picking
			cursor->setVisible(true);
			picker->update(camera);
		}
		else
		{
			// Hide the cursor and update first person camera
			cursor->setVisible(false);

			firstPersonCameraController->update(keyboard, mouse, lastFrameDuration);

			Ogre::Vector3 newCameraPosition;
			Ogre::Quaternion newCameraOrientation;

			firstPersonCameraController->getPositionOrientation(newCameraPosition, newCameraOrientation);

			camera->setPosition(newCameraPosition);
			camera->setOrientation(newCameraOrientation);
		}

		if (debuggerActive)
		{
			// Show the debug display if active (F1 to toggle)
			world->getDebugger().showDebugInformation();
		}

		// Write current FPS
		debug->write(20, 20, "FPS: " + Ogre::StringConverter::toString(window->getStatistics().lastFPS));
		
		// Update the screenwriter
		debug->update();

		// Render a frame
		ogre->renderOneFrame();
	}
}

bool Application::mouseMoved(const OIS::MouseEvent &evt)
{
	if(cursor != NULL)
	{
		// Update cursor position
		cursor->updatePosition(evt.state.X.abs, evt.state.Y.abs);
	}

	return true;
}

bool Application::mousePressed(const OIS::MouseEvent &evt, OIS::MouseButtonID)
{
	return true;
}

bool Application::mouseReleased(const OIS::MouseEvent &evt, OIS::MouseButtonID)
{
	return true;
}

bool Application::keyPressed(const OIS::KeyEvent &evt)
{
	return true;
}

bool Application::keyReleased(const OIS::KeyEvent &evt)
{
	if (evt.key == OIS::KC_ESCAPE)
	{
		// Request shutdown
		running = false;
	} 
	else if (evt.key == OIS::KC_F1)
	{
		// Toggle debug display
		if (debuggerActive)
		{
			debuggerActive = false;
			world->getDebugger().hideDebugInformation();
		}
		else
		{
			debuggerActive = true;
		}
	}
	else if (evt.key == OIS::KC_SPACE)
	{
		// Make a sphere and add impulse in the direction of the camera
		OgreNewt::Body* ball = PrimitiveFactory::getSingleton().createEllipsoid("", "", 10);
		ball->setPositionOrientation(camera->getPosition(), camera->getOrientation());
		ball->setContinuousCollisionMode(1);
		ball->addImpulse(camera->getDirection() * 50.0f, camera->getPosition());
	}

	return true;
}

void Application::windowResized(Ogre::RenderWindow* rw) 
{
	if (mouse)
	{
		// Notify OIS of new window size
		const OIS::MouseState& mouseState = mouse->getMouseState();
		mouseState.width = rw->getWidth();
		mouseState.height = rw->getHeight();
	}

	if (cursor)
	{
		// Also notify the cursor
		cursor->setWindowDimensions(rw->getWidth(), rw->getHeight());
	}
}

void Application::windowClosed(Ogre::RenderWindow* rw) 
{
	// Request shutdown when renderwindow is closed
	running = false;
}